﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Markup;

using alib;
using alib.Debugging;
using alib.Enumerable;
using alib.Reflection;
using alib.String;

using agree.schema;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// One line at a time, suitable for stdin sentence input or manual submitting. Press 'Ctrl-z' (Ctrl-d on Unix) or 
	/// 'break' to stop entering strings.
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class ConsoleReader : _monad, IActiveObj<IString>
	{
		public ConsoleReader(IRtParent parent)
			: base(parent, parent)
		{
		}

		public IDisposable Subscribe(IObserver<IString> observer)
		{
			// keep the agree subsystem alive while this is going on
			SysObj.BeginApplicationTask();

			this.observer = observer;
			Task.Factory.StartNew(_thread);
			return EmptyDisposable.Instance;
		}

		IObserver<IString> observer;

		void _thread()
		{
			while (true)
			{
				String s = Console.ReadLine();
				if (s == null)
					break;
				observer.OnNext(new StringItem(this, s));
			}
			observer.OnCompleted();

			/// done. remove our claim so the agree subsystem can exit when it becomes time to do so.
			SysObj.EndApplicationTask();
		}
	};

	public enum Alignment { Left = 0, Center, Right };

	public sealed class TabularRowDef : identity_base
	{
		public TabularRowDef(IIdentity prv = default(IIdentity)) : base(prv) { }

		public const int Remainder = -1;

		public String Heading { get; set; }

		public schema_prop_attachable Field { get; set; }

		public int Width { get; set; }

		public int MinWidth { get; set; }

		public uint Color { get; set; }

		public Alignment Align { get; set; }
	};


	public sealed class TabularDisplayDef : IdentList<TabularRowDef>
	{
		public TabularDisplayDef(IIdentity prv)
			: base(prv)
		{
			Clipping = true;
		}

		public bool Clipping { get; set; }

		//public bool f_clip_to_buffer = true;
	};
#if false
	public struct TabularRowEntry
	{
		public TabularRowDef row_def;
		public IIdentity item;
	};
	public sealed class TabularRow : IList<TabularRowEntry>, IIdentity
	{
		public TabularRow(IIdentity prv, TabularRowEntry[] arr)
		{
			this.id = Interlocked.Increment(ref SysObj.next_id) - 1;
			this.trace = prv;
			this.arr = arr;
		}
		readonly IIdentity trace;
		public IIdentity Trace { get { return trace; } }

		readonly TabularRowEntry[] arr;

		public int Count { get { return arr.Length; } }
		public bool IsReadOnly { get { return true; } }
		public int IndexOf(TabularRowEntry item) { return Array.IndexOf<TabularRowEntry>(arr, item); }
		public bool Contains(TabularRowEntry item) { return Array.IndexOf<TabularRowEntry>(arr, item) != -1; }

		public TabularRowEntry this[int index]
		{
			get { return arr[index]; }
			set { throw not.impl; }
		}

		public void CopyTo(TabularRowEntry[] array, int arrayIndex) { Array.Copy(arr, 0, array, arrayIndex, arr.Length); }
		public IEnumerator<TabularRowEntry> GetEnumerator() { return arr.Enumerator(); }
		IEnumerator IEnumerable.GetEnumerator() { return arr.GetEnumerator(); }
		public void Insert(int index, TabularRowEntry item) { throw not.valid; }
		public void RemoveAt(int index) { throw not.valid; }
		public void Add(TabularRowEntry item) { throw not.impl; }
		public void Clear() { throw not.impl; }
		public bool Remove(TabularRowEntry item) { throw not.impl; }

		public String Simple()
		{
			StringBuilder sb = new StringBuilder();
			foreach (var ent in arr)
			{
				//ent.row_def.Field.TryGetItemValue(ent.item,
				//sb.Append(ent.item,ent.row_def.
			}
			return sb.ToString();
		}
	};
#endif

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[ContentProperty("DisplayDefinition")]
	public sealed class FormatTabular : composition.f_primitive<IActiveObj<IIdentity>, IActiveObj<IString>>
	{
		//static readonly TabularRowDef[] _default_def =
		//{
		//	new TabularRowDef { 
		//		Heading="score", 
		//		Field=P.ParseSelectionScore, 
		//		Width=6,
		//		Align= Alignment.Right
		//	},
		//	new TabularRowDef { 
		//		Heading="ix", 
		//		Field=P.ItemIndex, 
		//		Width=4,
		//		Align= Alignment.Right
		//	},
		//	new TabularRowDef { 
		//		Heading="id",
		//		Field=P.ExternalId, 
		//		Width=5,
		//		Align= Alignment.Right
		//	},
		//	new TabularRowDef { 
		//		Heading="c_deriv", 
		//		Field=P.ExpectedDerivationCount, 
		//		Width=6,
		//		Align= Alignment.Right
		//	},
		//	new TabularRowDef { 
		//		Heading="Text", 
		//		Field=P.Text, 
		//		Width=TabularRowDef.Remainder,
		//		Color=0xFFd700,
		//	},
		//};

		public FormatTabular(IRtParent parent)
			: base(parent)
		{
			this.DisplayDefinition = new TabularDisplayDef(parent);
			//this.DisplayDefinition.AddRange(_default_def);
			this.AvailableWidth = 80;
		}
		//public FormatTabular(IIdentity prv, IActiveObj<IIdentity> items)
		//	: base(prv, items)
		//{
		//	this.DisplayDefinition = new TabularDisplayDef(prv);
		//	//this.DisplayDefinition.AddRange(_default_def);
		//	this.AvailableWidth = 80;
		//}

		public TabularDisplayDef DisplayDefinition { get; set; }

		public int AvailableWidth { get; set; }

		protected override IActiveObj<IString> func(IActiveObj<IIdentity> t)
		{
			return new _inner(this, t);
		}

		sealed class _inner : identity_node, IObserver<IIdentity>, IActiveObj<IString>
		{
			public _inner(FormatTabular outer, IActiveObj<IIdentity> items)
				: base(outer, items)
			{
				this.outer = outer;
				this.tdd = outer.DisplayDefinition;
				this.items = items;

				int used_width = tdd
								.Where(x => x.Width != TabularRowDef.Remainder)
								.Sum(x => x.Width)
								+ (tdd.Count - 1);

				this.remainder_width = Math.Max(0, outer.AvailableWidth - used_width);
			}
			FormatTabular outer;
			TabularDisplayDef tdd;
			IActiveObj<IIdentity> items;
			IObserver<IString> observer;
			int c_outstanding;
			int remainder_width;

			public IDisposable Subscribe(IObserver<IString> observer)
			{
				if (Interlocked.Exchange(ref this.observer, observer) != null)
					throw new Exception();
				this.c_outstanding = 1;
				var x = Interlocked.Exchange(ref this.items, null);
				var xas = x as IActiveObj<IString>;
				if (xas != null)
					return xas.Subscribe(this);
				return x.Subscribe(this);
			}

			void IObserver<IIdentity>.OnNext(IIdentity value)
			{
				Interlocked.Increment(ref c_outstanding);
				observer.OnNext(tag_row(value));
				if (Interlocked.Decrement(ref c_outstanding) == 0)
					Interlocked.Exchange(ref this.observer, null).OnCompleted();
			}

			void IObserver<IIdentity>.OnCompleted()
			{
				if (Interlocked.Decrement(ref c_outstanding) == 0)
					Interlocked.Exchange(ref this.observer, null).OnCompleted();
			}

			void IObserver<IIdentity>.OnError(Exception error)
			{
				var _tmp = Interlocked.Exchange(ref this.observer, null);
				if (_tmp != null)
				{
					c_outstanding = 0;
					_tmp.OnError(error);
				}
			}

			public IString tag_row(IIdentity item)
			{
				StringBuilder sb = new StringBuilder();
				foreach (var ent in tdd)
				{
					if (sb.Length > 0)
						sb.Append(' ');
					Object value;
					String txt;
					if ((value = ent.Field.GetValue(item)) != null)
						txt = value.ToString();
					//else if ((value = ent.Field.FindTracePropertyValue(item)) != null)	// broken for value types?
					//	txt = value.ToString();
					else
						txt = "";

					int w = ent.Width;
					if (w == TabularRowDef.Remainder)
						w = remainder_width;

					if (w >= 0)
					{
						if (txt.Length > w)
						{
							txt = txt.Left(w);
						}
						else
						{
							if (ent.Align == Alignment.Left)
								txt = txt.TrimEndOrPadRight(w);
							else if (ent.Align == Alignment.Right)
								txt = txt.TrimEndOrPadLeft(w);
							else
								throw not.impl;
						}
					}
					sb.Append(txt);
				}
				return new StringItem(this, sb.ToString());

				//int c = tdd.Count;
				//var arr = new TabularRowEntry[c];
				//var tr = new TabularRow(this, arr);
				//Object o;
				//for (int i = 0; i < c; i++)
				//    if ((arr[i].row_def = tdd[i]).Field.TryGetItemValue(item, out o))
				//        arr[i].item = o;
				//return tr;
			}


			//public IIdentity tag_row(IIdentity item)
			//{
			//    int c = tdd.Count;
			//    var arr = new TabularRowEntry[c];
			//    var tr = new TabularRow(this, arr);
			//    Object o;
			//    for (int i = 0; i < c; i++)
			//        if ((arr[i].row_def = tdd[i]).Field.TryGetItemValue(item, out o))
			//            arr[i].item = o;
			//    return tr;
			//}
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class ClientTabularDisplay : m_observable<IActiveObj<IIdentity>, IIdentity>, IObserver<IIdentity>
	{
		public ClientTabularDisplay(IRtParent parent, IActiveObj<IIdentity> input)
			: base(parent, input)
		{
		}

		protected override void start(IActiveObj<IIdentity> input)
		{
			input.Subscribe(this);
		}

		void IObserver<IIdentity>.OnNext(IIdentity item)
		{
			_add_task();
			String str = language_obj_utils.recover_string_content(item);
			Console.WriteLine(str);
			_remove_task();
		}

		void IObserver<IIdentity>.OnCompleted() { _remove_task(); }

		void IObserver<IIdentity>.OnError(Exception error) { _error(error); }
	};
}